package com.xxx.util;

import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.util.ClassUtils;

import java.io.IOException;
import java.util.HashSet;
import java.util.Set;

@Slf4j
public class ClassUtil {

    /**
     * 由于spring boot 打包为jar包，jersey packages 无法扫描jar对应的文件夹的文件，故自定义包扫描
     *
     * @return class[]
     */
    public static Set<Class<?>> findAllClasses(String... scanPackages) {
        Set<Class<?>> classes = new HashSet<>();
        for (String scanPackage : scanPackages) {
            ClassLoader loader = ClassUtil.class.getClassLoader();
            Resource[] resources = new Resource[0];
            try {
                resources = scan(loader, scanPackage);
            } catch (IOException e) {
                log.error("加载class异常", e);
            }
            classes.addAll(convert(loader, resources));
        }
        return classes;
    }

    /**
     * 扫描 jar 包
     *
     * @param loader      ClassLoader
     * @param packageName packageName
     */
    private static Resource[] scan(ClassLoader loader, String packageName) throws IOException {
        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(loader);
        String pattern = "classpath*:" + ClassUtils.convertClassNameToResourcePath(packageName) + "/*.class";
        return resolver.getResources(pattern);
    }

    /**
     * 加载 class
     *
     * @param loader   ClassLoader
     * @param resource resource
     */
    private static Class<?> loadClass(ClassLoader loader, Resource resource) {
        try {
            CachingMetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(loader);
            MetadataReader reader = metadataReaderFactory.getMetadataReader(resource);
            return ClassUtils.forName(reader.getClassMetadata().getClassName(), loader);
        } catch (LinkageError | ClassNotFoundException e) {
            if (log.isDebugEnabled()) {
                log.debug("Ignoring candidate class resource " + resource + " due to " + e);
            }
            return null;
        } catch (Throwable e) {
            if (log.isWarnEnabled()) {
                log.warn("Unexpected failure when loading class resource " + resource, e);
            }
            return null;
        }
    }

    /**
     * resources 转换为 Set<Class>
     *
     * @param loader    ClassLoader
     * @param resources Resource
     */
    private static Set<Class<?>> convert(ClassLoader loader, Resource[] resources) {
        Set<Class<?>> classSet = new HashSet<>(resources.length);
        for (Resource resource : resources) {
            Class<?> clazz = loadClass(loader, resource);
            if (clazz != null) {
                classSet.add(clazz);
            }
        }
        return classSet;
    }

}
